/**
 * PlayersTable.java
 * 
 * @author Beatrice Bacelli
 * @author Luca Bedogni
 * @author Silvia Righini
 */
package unibo.lsb.logic;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * The complete table with all the information about players.. each player has
 * its "row", a {@link Player}. Implemented as a list of {@link Player}.
 * 
 */
public class PlayersTable implements Serializable {

	public static final int PLAYERS_ALL_LOSE = -1;
	public static final int PLAYERS_LOT_ALIVE = -2;
	public static final int PLAYERS_ALL_DEAD_BUT_ME = -3;
	public static final int PLAYERS_ALIVE_CHECK_ERR = -4;
	/**
	 * Number of penalties that excludes a player from the game.
	 * 
	 * @config
	 */
	public static final int MAX_PENALTIES = 5;

	public static int getMAX_PENALTIES() {
		return MAX_PENALTIES;
	}

	List<Player> list;
	int turn;
	int numberOfPlayers;

	/**
	 * Creates table, all values not already present in the the list of PlayerId
	 * are initialized to zero.
	 * 
	 * @param listOfPlayerIds
	 * 
	 * @param listOfPlayers
	 */
	public PlayersTable(List<PlayerId> listOfPlayerIds) {
		super();
		int nOfPlayers = 0;
		this.list = new ArrayList<Player>();
		Iterator<PlayerId> i = listOfPlayerIds.iterator();
		while (i.hasNext()) {
			list.add(new Player(i.next(), 0, 0));
			nOfPlayers++;
		}
		numberOfPlayers = nOfPlayers;
		this.turn = 0;
	}

	/**
	 * Returns penalty for a given player.
	 * 
	 * @param player
	 * @return
	 */
	public int getPenaltyForPlayer(PlayerId player) {
		boolean found = false;
		int penalty = 0;

		Iterator<Player> i = list.iterator();
		while (i.hasNext() && !found) {
			Player p = i.next();
			PlayerId pi = p.getPlayerId();
			if (pi.equals(player)) {
				penalty = p.getPenalty();
				found = true;
			}
		}
		if (!found) {
			throw new IllegalArgumentException("Player id "
					+ player.getIpAddress() + ":" + player.getName()
					+ " not in players' table.");
		}
		return penalty;
	}

	/**
	 * Returns penalty for a given player.
	 * 
	 * @param player
	 * @return
	 */
	private int getPenaltyForPlayer(Player player) {
		return getPenaltyForPlayer(player.getPlayerId());
	}

	/**
	 * Returns the list of the players
	 * 
	 * @return
	 */
	public List<Player> getPlayers() {
		return list;
	}

	/**
	 * Players table is updated, adding the given {@link LastAssignedPenalty} to
	 * the right player.
	 * 
	 * @param penalty
	 * @return boolean false if assign the maximum penalty
	 */
	public boolean computePenalty(LastAssignedPenalty penalty) {
		Boolean found = false;
		int penaltyInTable = 0;
		PlayerId player = penalty.getPlayer();
		int elementCounter = -1;
		Boolean ret = true;

		Iterator<Player> i = list.iterator();

		while (i.hasNext() && !found) {
			elementCounter++;
			Player p = i.next();
			PlayerId pi = p.getPlayerId();

			if (pi.equals(player)) {
				/* found the player */
				found = true;
				penaltyInTable = p.getPenalty();
				penaltyInTable = penaltyInTable + penalty.getPenalty();
				if (penaltyInTable >= MAX_PENALTIES) {
					penaltyInTable = MAX_PENALTIES;
					ret = false;
				}
				Player updatedPlayer = getPlayer(elementCounter);
				updatedPlayer.setPenalty(penaltyInTable);
				list.set(elementCounter, updatedPlayer);
			}
		}
		if (!found) {
			throw new IllegalArgumentException("Player id "
					+ player.getIpAddress() + ":" + player.getName()
					+ " not in players' table.");
		}
		this.toString();
		return ret;
	}

	/**
	 * Returns the player corresponding to a given turn/index
	 */
	public Player getPlayer(final int index) {
		return list.get(index);
	}

	/**
	 * Computes the number of the turn of a given player. Same as
	 * {@link #getTurnForPlayer(PlayerId)}, but takes a {@link Player} instead.
	 * 
	 * @param player
	 *            a player
	 * @return the index of the player in the PlayersTable (i.e. its turn)
	 */
	public int getTurnForPlayer(Player player) {
		int n = -1;
		boolean found = false;
		Iterator<Player> ite = list.iterator();
		while (ite.hasNext() && !found) {
			Player p = ite.next();
			n++;
			if (p.equals(player)) {
				found = true;
			}
		}
		if (found == false) {
			n = -1;
		}
		return n;
	}

	/**
	 * Computes the number of the turn of a given player. Same as
	 * {@link #getTurnForPlayer(Player)}, but takes a {@link PlayerId} instead.
	 * 
	 * @param player
	 *            a player
	 * @return the index of the player in the PlayersTable (i.e. its turn)
	 */

	public int getTurnForPlayer(PlayerId player) {
		int n = -1;
		boolean found = false;
		Iterator<Player> ite = list.iterator();
		while (ite.hasNext() && !found) {
			PlayerId p = ite.next().getPlayerId();
			n++;
			if (p.equals(player)) {
				found = true;
			}
		}
		if (found == false) {
			n = -1;
		}
		return n;
	}

	/**
	 * Returns player corresponding to given turn. Prints an error on the stderr
	 * and returns null if the int turn is not a valid index.
	 * 
	 * @param turn
	 * @return Player whose turn is 'turn'.
	 */
	public Player getPlayerCorrespondingToTurn(final int turn) {
		Player player = null;
		if ((turn < 0) || (turn > list.size())) {
			System.err.println("Error: requested player corresponding to turn "
					+ turn + " in a PlayersTable of " + list.size()
					+ " players.");
		} else {
			player = list.get(turn);
		}
		return player;
	}

	/**
	 * Returns the next alive player, starting check from the turn set in the
	 * PlayersTable itself. If returned value is null, it means that no other
	 * player is alive but the current one.
	 * 
	 * @return
	 */
	public Player getNextAlivePlayer() {
		return getNextAlivePlayer(turn);
	}

	/**
	 * Calculates the next alive player after a player corresponding to turn
	 * index. If returned value is null, it means that no other player is alive
	 * but the current one.
	 * 
	 * @param index
	 * @return
	 */
	public Player getNextAlivePlayer(int index) {

		int n = 0; // increment on turn
		boolean foundAlivePlayer = false;
		Player player;

		do {
			player = nextTurn(index + n);
			if (player.isAlive() && (player.getPenalty() < MAX_PENALTIES)) {
				foundAlivePlayer = true;
			} else {
				n++;
			}
		} while (!foundAlivePlayer && !(n > numberOfPlayers));

		if (n == numberOfPlayers) {
			System.err.println("Everyone crashed but me!");
			player = null;
		}
		return player;
	}

	/**
	 * Returns the turn.
	 * 
	 * @return
	 */
	public int getTurn() {
		return turn;
	}

	/**
	 * Sets a new turn.
	 * 
	 * @param turn
	 *            a value between 0 and the length of the list.
	 */
	public void setTurn(int turn) {
		if ((turn < 0) || (turn > numberOfPlayers)) {
			throw new RuntimeException("Error: trying to set turn to " + turn
					+ " in a PlayersTable of " + numberOfPlayers + " players.");
		}
		this.turn = turn;
	}

	/**
	 * Returns the number corresponding to the next player to play - based on
	 * the value of turn memorized in the PlayersTable.
	 * 
	 * @return (n + 1) % number of players
	 */
	public Player nextTurn() {
		return getPlayer(((turn + 1) % numberOfPlayers));
	}

	/**
	 * Returns the number corresponding to the next player which is supposed to
	 * play - based on the given turn 'n'.
	 * 
	 * @param n
	 *            some turn
	 * @return (n + 1) % number of players
	 */
	public Player nextTurn(int n) {
		// if (n < 0 || n > numberOfPlayers) {
		// throw new RuntimeException(
		// "PlayersTable: invalid request for nextTurn(). Asked " + n
		// + " on a table of " + numberOfPlayers + " players.");
		// }
		return getPlayer(((n + 1) % numberOfPlayers));
	}

	@Override
	public String toString() {
		return UtilsList.printList(list);
	}

	/**
	 * Checks the number of players still in the game (alive and with less than
	 * {@link #MAX_PENALTIES}) in the table.
	 * 
	 * @return {@link #PLAYERS_ALL_LOSE} if no valid player is found (everyone
	 *         dead or reached MAX PENALTIES), {@link #PLAYERS_ALL_DEAD_BUT_ME}
	 *         if I am the only alive player left, {@link #PLAYERS_LOT_ALIVE} if
	 *         more than one alive player is found,
	 *         {@link #PLAYERS_ALIVE_CHECK_ERR} if an error in counting
	 *         occurred. If only one last standing player is found, its index is
	 *         returned.
	 */
	public int checkLastOneStanding() {
		int returnValue;
		int indexOfStandingPlayer = -1;

		int i = -1;
		int valid = numberOfPlayers;
		int alive = numberOfPlayers;
		Iterator<Player> ite = list.iterator();
		// counting
		while (ite.hasNext()) {
			Player p = ite.next();
			i++;
			// player is dead
			if (!p.isAlive()) {
				alive--;
			}
			if (!p.isAlive() || (p.getPenalty() >= MAX_PENALTIES)) {
				valid--;
			} else {
				// keeping track of the alive one
				indexOfStandingPlayer = i;
			}
		}

		// if everyone is dead but me this is the only thing that counts
		if (alive == 1) {
			int self = PlayerSelfSingleton.getInstance().getSelf()
					.getIndexInTable();
			returnValue = PLAYERS_ALL_DEAD_BUT_ME;
			if (self != indexOfStandingPlayer) {
				System.err
						.println("Something weird happened, looks like I am not alive!");
			}
		} else {
			if (valid == 0) {
				returnValue = PLAYERS_ALL_LOSE;
			} else if (valid == 1) {
				returnValue = indexOfStandingPlayer;
			} else if (valid > 1) {
				// more than one alive player
				returnValue = PLAYERS_LOT_ALIVE;
			} else {
				returnValue = PLAYERS_ALIVE_CHECK_ERR;
				System.err
						.println("Something weird happened, checkLastOneStanding counted "
								+ valid + "players.");
			}
		}
		return returnValue;
	}
}
